#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
工具函数模块

提供各种实用工具函数，包括数据验证、格式转换、
错误处理等功能。
"""

import re
import json
from typing import Any, Dict, List, Optional, Union, Tuple
from datetime import datetime, date, time
from decimal import Decimal
import validators


def validate_port(port: Union[str, int]) -> int:
    """
    验证端口号
    
    Args:
        port: 端口号（字符串或整数）
        
    Returns:
        int: 验证后的端口号
        
    Raises:
        ValueError: 当端口号无效时
    """
    try:
        port_int = int(port)
        if not (1 <= port_int <= 65535):
            raise ValueError(f"端口号必须在 1-65535 范围内，当前值: {port_int}")
        return port_int
    except (ValueError, TypeError) as e:
        raise ValueError(f"无效的端口号: {port}") from e


def validate_host(host: str) -> str:
    """
    验证主机地址
    
    Args:
        host: 主机地址
        
    Returns:
        str: 验证后的主机地址
        
    Raises:
        ValueError: 当主机地址无效时
    """
    if not host or not isinstance(host, str):
        raise ValueError("主机地址不能为空")
    
    host = host.strip()
    
    # 检查是否为有效的 IP 地址、域名或 localhost
    if not (validators.ip_address(host) or validators.domain(host) or host == "localhost"):
        raise ValueError(f"无效的主机地址: {host}")
    
    return host


def validate_database_name(name: str) -> str:
    """
    验证数据库名称
    
    Args:
        name: 数据库名称
        
    Returns:
        str: 验证后的数据库名称
        
    Raises:
        ValueError: 当数据库名称无效时
    """
    if not name or not isinstance(name, str):
        raise ValueError("数据库名称不能为空")
    
    name = name.strip()
    
    # 检查数据库名称格式（MySQL 规则）
    if not re.match(r'^[a-zA-Z0-9_$]+$', name):
        raise ValueError(f"无效的数据库名称: {name}. 只能包含字母、数字、下划线和美元符号")
    
    if len(name) > 64:
        raise ValueError(f"数据库名称过长: {name}. 最大长度为 64 个字符")
    
    return name


def validate_table_name(name: str) -> str:
    """
    验证表名称
    
    Args:
        name: 表名称
        
    Returns:
        str: 验证后的表名称
        
    Raises:
        ValueError: 当表名称无效时
    """
    if not name or not isinstance(name, str):
        raise ValueError("表名称不能为空")
    
    name = name.strip()
    
    # 检查表名称格式（MySQL 规则）
    if not re.match(r'^[a-zA-Z0-9_$]+$', name):
        raise ValueError(f"无效的表名称: {name}. 只能包含字母、数字、下划线和美元符号")
    
    if len(name) > 64:
        raise ValueError(f"表名称过长: {name}. 最大长度为 64 个字符")
    
    return name


def sanitize_sql_identifier(identifier: str) -> str:
    """
    清理 SQL 标识符（表名、列名等）
    
    Args:
        identifier: SQL 标识符
        
    Returns:
        str: 清理后的标识符
        
    Raises:
        ValueError: 当标识符无效时
    """
    if not identifier or not isinstance(identifier, str):
        raise ValueError("SQL 标识符不能为空")
    
    identifier = identifier.strip()
    
    # 移除反引号
    identifier = identifier.strip('`')
    
    # 验证标识符格式
    if not re.match(r'^[a-zA-Z0-9_$]+$', identifier):
        raise ValueError(f"无效的 SQL 标识符: {identifier}")
    
    # 添加反引号保护
    return f"`{identifier}`"


def format_sql_value(value: Any) -> str:
    """
    格式化 SQL 值
    
    Args:
        value: 要格式化的值
        
    Returns:
        str: 格式化后的 SQL 值
    """
    if value is None:
        return "NULL"
    elif isinstance(value, bool):
        return "TRUE" if value else "FALSE"
    elif isinstance(value, (int, float, Decimal)):
        return str(value)
    elif isinstance(value, str):
        # 转义单引号
        escaped = value.replace("'", "''")
        return f"'{escaped}'"
    elif isinstance(value, (datetime, date, time)):
        return f"'{value}'"
    elif isinstance(value, (list, dict)):
        # 将复杂对象转换为 JSON 字符串
        json_str = json.dumps(value, ensure_ascii=False, default=str)
        escaped = json_str.replace("'", "''")
        return f"'{escaped}'"
    else:
        # 其他类型转换为字符串
        str_value = str(value).replace("'", "''")
        return f"'{str_value}'"


def parse_connection_string(connection_string: str) -> Dict[str, Any]:
    """
    解析数据库连接字符串
    
    Args:
        connection_string: 连接字符串，格式如 "mysql://user:password@host:port/database"
        
    Returns:
        Dict[str, Any]: 解析后的连接参数
        
    Raises:
        ValueError: 当连接字符串格式无效时
    """
    # 正则表达式匹配连接字符串
    pattern = r'^mysql://([^:]+):([^@]+)@([^:]+):?(\d+)?/(.+)$'
    match = re.match(pattern, connection_string)
    
    if not match:
        raise ValueError(f"无效的连接字符串格式: {connection_string}")
    
    user, password, host, port, database = match.groups()
    
    # 验证和转换参数
    config = {
        'user': user,
        'password': password,
        'host': validate_host(host),
        'database': validate_database_name(database),
    }
    
    # 处理端口号
    if port:
        config['port'] = validate_port(port)
    else:
        config['port'] = 3306  # MySQL 默认端口
    
    return config


def build_connection_string(
    user: str,
    password: str,
    host: str,
    port: int,
    database: str
) -> str:
    """
    构建数据库连接字符串
    
    Args:
        user: 用户名
        password: 密码
        host: 主机地址
        port: 端口号
        database: 数据库名称
        
    Returns:
        str: 连接字符串
    """
    return f"mysql://{user}:{password}@{host}:{port}/{database}"


def mask_sensitive_data(data: Dict[str, Any], sensitive_keys: Optional[List[str]] = None) -> Dict[str, Any]:
    """
    脱敏敏感数据
    
    Args:
        data: 原始数据字典
        sensitive_keys: 敏感字段列表
        
    Returns:
        Dict[str, Any]: 脱敏后的数据字典
    """
    if sensitive_keys is None:
        sensitive_keys = ['password', 'passwd', 'secret', 'token', 'key']
    
    masked_data = data.copy()
    
    for key, value in masked_data.items():
        if isinstance(key, str) and any(sensitive in key.lower() for sensitive in sensitive_keys):
            if isinstance(value, str) and value:
                masked_data[key] = "***"
            else:
                masked_data[key] = "***"
        elif isinstance(value, dict):
            masked_data[key] = mask_sensitive_data(value, sensitive_keys)
    
    return masked_data


def convert_mysql_result_to_dict(cursor_result: List[Tuple], column_names: List[str]) -> List[Dict[str, Any]]:
    """
    将 MySQL 查询结果转换为字典列表
    
    Args:
        cursor_result: 游标查询结果
        column_names: 列名列表
        
    Returns:
        List[Dict[str, Any]]: 字典格式的查询结果
    """
    if not cursor_result:
        return []
    
    return [
        dict(zip(column_names, row))
        for row in cursor_result
    ]


def serialize_mysql_value(value: Any) -> Any:
    """
    序列化 MySQL 值为 JSON 兼容格式
    
    Args:
        value: MySQL 查询结果中的值
        
    Returns:
        Any: JSON 兼容的值
    """
    if value is None:
        return None
    elif isinstance(value, (datetime, date)):
        return value.isoformat()
    elif isinstance(value, time):
        return str(value)
    elif isinstance(value, Decimal):
        return float(value)
    elif isinstance(value, bytes):
        try:
            return value.decode('utf-8')
        except UnicodeDecodeError:
            return value.hex()
    else:
        return value


def serialize_mysql_results(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """
    序列化 MySQL 查询结果为 JSON 兼容格式
    
    Args:
        results: MySQL 查询结果列表
        
    Returns:
        List[Dict[str, Any]]: 序列化后的结果列表
    """
    return [
        {key: serialize_mysql_value(value) for key, value in row.items()}
        for row in results
    ]


def validate_sql_query(query: str) -> bool:
    """
    基本的 SQL 查询验证
    
    Args:
        query: SQL 查询语句
        
    Returns:
        bool: 查询是否有效
        
    Raises:
        ValueError: 当查询包含危险操作时
    """
    if not query or not isinstance(query, str):
        raise ValueError("SQL 查询不能为空")
    
    query_upper = query.upper().strip()
    
    # 检查危险操作
    dangerous_keywords = [
        'DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT', 'UPDATE',
        'GRANT', 'REVOKE', 'LOAD_FILE', 'INTO OUTFILE', 'INTO DUMPFILE'
    ]
    
    for keyword in dangerous_keywords:
        if keyword in query_upper:
            raise ValueError(f"查询包含危险操作: {keyword}")
    
    # 检查是否为 SELECT 查询
    if not query_upper.startswith('SELECT'):
        raise ValueError("只允许 SELECT 查询")
    
    return True


def format_error_message(error: Exception, context: Optional[str] = None) -> str:
    """
    格式化错误消息
    
    Args:
        error: 异常对象
        context: 错误上下文
        
    Returns:
        str: 格式化后的错误消息
    """
    error_type = type(error).__name__
    error_message = str(error)
    
    if context:
        return f"[{context}] {error_type}: {error_message}"
    else:
        return f"{error_type}: {error_message}"


def chunk_list(lst: List[Any], chunk_size: int) -> List[List[Any]]:
    """
    将列表分块
    
    Args:
        lst: 原始列表
        chunk_size: 块大小
        
    Returns:
        List[List[Any]]: 分块后的列表
    """
    if chunk_size <= 0:
        raise ValueError("块大小必须大于 0")
    
    return [lst[i:i + chunk_size] for i in range(0, len(lst), chunk_size)]


def safe_json_dumps(obj: Any, **kwargs) -> str:
    """
    安全的 JSON 序列化
    
    Args:
        obj: 要序列化的对象
        **kwargs: json.dumps 的其他参数
        
    Returns:
        str: JSON 字符串
    """
    def default_serializer(o):
        if isinstance(o, (datetime, date)):
            return o.isoformat()
        elif isinstance(o, time):
            return str(o)
        elif isinstance(o, Decimal):
            return float(o)
        elif isinstance(o, bytes):
            try:
                return o.decode('utf-8')
            except UnicodeDecodeError:
                return o.hex()
        else:
            return str(o)
    
    kwargs.setdefault('default', default_serializer)
    kwargs.setdefault('ensure_ascii', False)
    
    return json.dumps(obj, **kwargs)


def get_mysql_version_info(version_string: str) -> Dict[str, Any]:
    """
    解析 MySQL 版本信息
    
    Args:
        version_string: MySQL 版本字符串
        
    Returns:
        Dict[str, Any]: 版本信息字典
    """
    # 提取版本号
    version_match = re.match(r'(\d+)\.(\d+)\.(\d+)', version_string)
    
    if version_match:
        major, minor, patch = map(int, version_match.groups())
        return {
            'full_version': version_string,
            'major': major,
            'minor': minor,
            'patch': patch,
            'version_tuple': (major, minor, patch)
        }
    else:
        return {
            'full_version': version_string,
            'major': 0,
            'minor': 0,
            'patch': 0,
            'version_tuple': (0, 0, 0)
        }